/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.explorer.view; import java.awt.event.*; import java.awt.*; import java.beans.*; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.awt.dnd.DnDConstants; import java.util.HashSet; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openide.awt.MouseUtils; import org.openide.explorer.*; import org.openide.util.WeakListener; import org.openide.util.HelpCtx; import org.openide.util.actions.SystemAction; import org.openide.util.actions.Presenter; import org.openide.nodes.*; import org.openide.util.Utilities; /** Explorer view to display items in a list. * @author Ian Formanek, Jan Jancura, Jaroslav Tulach */ public class ListView extends JScrollPane implements Externalizable { /** generated Serialized Version UID */ static final long serialVersionUID = -7540940974042262975L; /** Explorer manager to work with. Is not null only if the component is showing * in components hierarchy */ private transient ExplorerManager manager; /** The actual JList list */ transient protected JList list; /** model to use */ transient protected NodeListModel model; // // listeners // /** Listener to nearly everything */ transient Listener managerListener; /** weak variation of the listener for property change on the explorer manager */ transient PropertyChangeListener wlpc; /** weak variation of the listener for vetoable change on the explorer manager */ transient VetoableChangeListener wlvc; /** popup */ transient PopupAdapter popupListener; // // properties // /** if true, the icon view displays a popup on right mouse click, if false, the popup is not displayed */ private boolean popupAllowed = true; /** if true, the hierarchy traversal is allowed, if false, it is disabled */ private boolean traversalAllowed = true; /** action preformer */ private ActionListener defaultProcessor; // // Dnd // /** true if drag support is active */ transient boolean dragActive = false; /** true if drop support is active */ transient boolean dropActive = false; /** Drag support */ transient ListViewDragSupport dragSupport; /** Drop support */ transient ListViewDropSupport dropSupport; // init ................................................................................. /** Default constructor. */ public ListView() { initializeList (); // DnD not stable now... //setDragSource(true); //setDropTarget(true); } /** Initializes the tree & model. */ private void initializeList () { // initilizes the JTree model = createModel (); list = createList (); list.setModel (model); setViewportView (list); { AbstractAction action = new GoUpAction (); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0); list.registerKeyboardAction(action, key, JComponent.WHEN_FOCUSED); } { AbstractAction action = new EnterAction (); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); list.registerKeyboardAction(action, key, JComponent.WHEN_FOCUSED); } managerListener = new Listener (); popupListener = new PopupAdapter (); list.addMouseListener(managerListener); list.addMouseListener(popupListener); list.getSelectionModel().setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); ToolTipManager.sharedInstance ().registerComponent (list); } /* * Write view's state to output stream. */ public void writeExternal (ObjectOutput out) throws IOException { out.writeObject (new Boolean (popupAllowed)); out.writeObject (new Boolean (traversalAllowed)); out.writeObject (new Integer (getSelectionMode ())); } /* * Reads view's state form output stream. */ public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { popupAllowed = ((Boolean)in.readObject ()).booleanValue (); traversalAllowed = ((Boolean)in.readObject ()).booleanValue (); setSelectionMode (((Integer)in.readObject ()).intValue ()); } // properties ........................................................................... /** Test whether display of a popup menu is enabled. * @return <code>true</code> if so */ public boolean isPopupAllowed () { return popupAllowed; } /** Enable/disable displaying popup menus on list view items. Default is enabled. * @param value <code>true</code> to enable */ public void setPopupAllowed (boolean value) { popupAllowed = value; } /** Test whether hierarchy traversal shortcuts are permitted. * @return <code>true</code> if so */ public boolean isTraversalAllowed () { return traversalAllowed; } /** Enable/disable hierarchy traversal using <code>CTRL+click</code> (down) and <code>Backspace</code> (up), default is enabled. * @param value <code>true</code> to enable */ public void setTraversalAllowed (boolean value) { traversalAllowed = value; } /** Get the current processor for default actions. * If not <code>null</code>, double-clicks or pressing Enter on * items in the view will not perform the default action on the selected node; rather the processor * will be notified about the event. * @return the current default-action processor, or <code>null</code> */ public ActionListener getDefaultProcessor () { return defaultProcessor; } /** Set a new processor for default actions. * @param value the new default-action processor, or <code>null</code> to restore use of the selected node's declared default action * @see #getDefaultProcessor */ public void setDefaultProcessor (ActionListener value) { defaultProcessor = value; } /** * Set whether single-item or multiple-item * selections are allowed. * @param selectionMode one of {@link ListSelectionModel#SINGLE_SELECTION}, {@link ListSelectionModel#SINGLE_INTERVAL_SELECTION}, or {@link ListSelectionModel#MULTIPLE_INTERVAL_SELECTION} * @see ListSelectionModel#setSelectionMode * @beaninfo * description: The selection mode. * enum: SINGLE_SELECTION ListSelectionModel.SINGLE_SELECTION * SINGLE_INTERVAL_SELECTION ListSelectionModel.SINGLE_INTERVAL_SELECTION * MULTIPLE_INTERVAL_SELECTION ListSelectionModel.MULTIPLE_INTERVAL_SELECTION */ public void setSelectionMode(int selectionMode) { list.setSelectionMode(selectionMode); } /** Get the selection mode. * @return the mode * @see #setSelectionMode */ public int getSelectionMode() { return list.getSelectionMode(); } /********** Support for the Drag & Drop operations *********/ /** @return true if dragging from the view is enabled, false * otherwise.<br> * Drag support is disabled by default. */ public boolean isDragSource () { return dragActive; } /** Enables/disables dragging support. * @param state true enables dragging support, false disables it. */ public void setDragSource (boolean state) { if (state == dragActive) return; dragActive = state; // create drag support if needed if (dragActive && (dragSupport == null)) dragSupport = new ListViewDragSupport(this, list); // activate / deactivate support according to the state dragSupport.activate(dragActive); } /** @return true if dropping to the view is enabled, false * otherwise<br> * Drop support is disabled by default. */ public boolean isDropTarget () { return dropActive; } /** Enables/disables dropping support. * @param state true means drops into view are allowed, * false forbids any drops into this view. */ public void setDropTarget (boolean state) { if (state == dropActive) return; dropActive = state; // create drop support if needed if (dropActive && (dropSupport == null)) dropSupport = new ListViewDropSupport(this, list); // activate / deactivate support according to the state dropSupport.activate(dropActive); } /** @return Set of actions which are allowed when dragging from * asociated component. * Actions constants comes from DnDConstants.XXX constants. * All actions (copy, move, link) are allowed by default. */ public int getAllowedDragActions () { // PENDING return DnDConstants.ACTION_MOVE | DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK; } /** Sets allowed actions for dragging * @param actions new drag actions, using DnDConstants.XXX */ public void setAllowedDragActions (int actions) { // PENDING } /** @return Set of actions which are allowed when dropping * into the asociated component. * Actions constants comes from DnDConstants.XXX constants. * All actions are allowed by default. */ public int getAllowedDropActions () { // PENDING return DnDConstants.ACTION_MOVE | DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK; } /** Sets allowed actions for dropping. * @param actions new allowed drop actions, using DnDConstants.XXX */ public void setAllowedDropActions (int actions) { // PENDING } // // Methods to override // /** Creates the list that will display the data. */ protected JList createList () { JList list = new NbList (); list.setCellRenderer(NodeRenderer.sharedInstance ()); return list; } /** Allows subclasses to change the default model used for * the list. */ protected NodeListModel createModel () { return new NodeListModel (); } /** Called when the list changed selection and the explorer manager * should be updated. * @param nodes list of nodes that should be selected * @param em explorer manager * @exception PropertyVetoException if the manager does not allow the * selection */ protected void selectionChanged (Node[] nodes, ExplorerManager em) throws PropertyVetoException { em.setSelectedNodes (nodes); } /** Called when explorer manager is about to change the current selection. * The view can forbid the change if it is not able to display such * selection. * * @param nodes the nodes to select * @return false if the view is not able to change the selection */ protected boolean selectionAccept (Node[] nodes) { // if the selection is just the root context, confirm the selection if (nodes.length == 1 && manager.getRootContext().equals(nodes[0])) { return true; } Node cntx = manager.getExploredContext (); // we do not allow selection in other than the exploredContext for (int i = 0; i < nodes.length; i++) { VisualizerNode v = VisualizerNode.getVisualizer (null, nodes[i]); if (model.getIndex (v) == -1) { return false; } } return true; } /** Shows selection. * @param indexes indexes of objects to select */ protected void showSelection (int[] indexes) { list.setSelectedIndices (indexes); } // // Working methods // /* Initilizes the view. */ public void addNotify () { super.addNotify (); // run under mutex ExplorerManager em = ExplorerManager.find (this); if (em != manager) { if (manager != null) { manager.removeVetoableChangeListener (wlvc); manager.removePropertyChangeListener (wlpc); } manager = em; manager.addVetoableChangeListener(wlvc = WeakListener.vetoableChange (managerListener, manager)); manager.addPropertyChangeListener(wlpc = WeakListener.propertyChange (managerListener, manager)); model.setNode (manager.getExploredContext ()); updateSelection(); }; list.getSelectionModel ().addListSelectionListener (managerListener); } /** Removes listeners. */ public void removeNotify () { super.removeNotify (); list.getSelectionModel ().removeListSelectionListener (managerListener); } /** This method is called when user double-clicks on some object or * presses Enter key. * @param index Index of object in current explored context */ final void performObjectAt(int index, int modifiers) { if (index < 0 || index >= model.getSize ()) { return; } VisualizerNode v = (VisualizerNode)model.getElementAt (index); Node node = v.node; // if DefaultProcessor is set, the default action is notified to it overriding the default action on nodes if (defaultProcessor != null) { defaultProcessor.actionPerformed (new ActionEvent (node, 0, null, modifiers)); return; } // on double click - invoke default action, if there is any // (unless user holds CTRL key what means that we should always dive into the context) SystemAction sa = node.getDefaultAction (); if (sa != null && (modifiers & java.awt.event.InputEvent.CTRL_MASK) == 0) { sa.actionPerformed (new ActionEvent (node, ActionEvent.ACTION_PERFORMED, "")); // NOI18N } // otherwise dive into the context else if (traversalAllowed && (!node.isLeaf())) manager.setExploredContext (node); } /** Called when selection has been changed. */ private void updateSelection() { Node[] sel = manager.getSelectedNodes (); int[] indices = new int[sel.length]; for (int i = 0; i < sel.length; i++) { VisualizerNode v = VisualizerNode.getVisualizer (null, sel[i]); indices[i] = model.getIndex (v); } manager.removePropertyChangeListener (wlpc); manager.removeVetoableChangeListener (wlvc); try { showSelection (indices); } finally { manager.addPropertyChangeListener (wlpc); manager.addVetoableChangeListener (wlvc); } } // innerclasses ......................................................................... /** Enhancement of standart JList. */ final class NbList extends AutoscrollJList { static final long serialVersionUID =-7571829536335024077L; /** * Overrides JComponent's getToolTipText method in order to allow * renderer's tips to be used if it has text set. * <p> * NOTE: For JTree to properly display tooltips of its renderers * JTree must be a registered component with the ToolTipManager. * This can be done by invoking * <code>ToolTipManager.sharedInstance().registerComponent(tree)</code>. * This is not done automaticly! * * @param event the MouseEvent that initiated the ToolTip display */ public String getToolTipText (MouseEvent event) { if (event != null) { Point p = event.getPoint (); int row = locationToIndex (p); if (row >= 0) { VisualizerNode v = (VisualizerNode)model.getElementAt (row); String tooltip = v.shortDescription; String displayName = v.displayName; if ((tooltip != null) && !tooltip.equals (displayName)) return tooltip; } } return null; } } /** Popup menu listener. */ private final class PopupAdapter extends org.openide.awt.MouseUtils.PopupMouseAdapter { protected void showPopup (MouseEvent e) { if (manager == null) return; int i = list.locationToIndex (new Point (e.getX (), e.getY ())); if (!list.isSelectedIndex (i)) list.setSelectedIndex (i); list.requestFocus (); if (!popupAllowed) return; JPopupMenu popup = NodeOp.findContextMenu(manager.getSelectedNodes()); if ((popup != null) && (popup.getSubElements().length > 0)) { java.awt.Point p = getViewport().getViewPosition(); p.x = e.getX() - p.x; p.y = e.getY() - p.y; SwingUtilities.convertPointToScreen (p, ListView.this); Dimension popupSize = popup.getPreferredSize (); Dimension screenSize = Toolkit.getDefaultToolkit ().getScreenSize (); if (p.x + popupSize.width > screenSize.width) p.x = screenSize.width - popupSize.width; if (p.y + popupSize.height > screenSize.height) p.y = screenSize.height - popupSize.height; SwingUtilities.convertPointFromScreen (p, ListView.this); popup.show(ListView.this, p.x, p.y); } } } // end of PopupAdapter /** */ private final class Listener extends MouseAdapter implements ListSelectionListener, PropertyChangeListener, VetoableChangeListener { public void mouseClicked(MouseEvent e) { if (MouseUtils.isDoubleClick(e)) { int index = list.locationToIndex(e.getPoint()); performObjectAt(index, e.getModifiers()); } } public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { if (manager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) { Node[] newNodes = (Node[])evt.getNewValue(); if (!selectionAccept (newNodes)) { throw new PropertyVetoException("", evt); // NOI18N } } } public void propertyChange(PropertyChangeEvent evt) { if (manager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) { updateSelection(); return; } if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) { model.setNode (manager.getExploredContext ()); //System.out.println("Children: " + java.util.Arrays.asList (list.getValues ())); // NOI18N return; } } public void valueChanged(ListSelectionEvent e) { Object[] values = list.getSelectedValues (); Node[] nodes = new Node[values.length]; for (int i = 0; i < nodes.length; i++) { nodes[i] = Visualizer.findNode (values[i]); } list.getSelectionModel ().removeListSelectionListener (this); try { selectionChanged (nodes, manager); } catch (java.beans.PropertyVetoException ex) { // selection vetoed - restore previous selection updateSelection(); } finally { list.getSelectionModel ().addListSelectionListener (this); } } } // Backspace jumps to parent folder of explored context private final class GoUpAction extends AbstractAction { static final long serialVersionUID =1599999335583246715L; public GoUpAction () { super ("GoUpAction"); // NOI18N } public void actionPerformed(ActionEvent e) { if (traversalAllowed) { Node pan = manager.getExploredContext(); pan = pan.getParentNode(); if (pan != null) manager.setExploredContext(pan); } } public boolean isEnabled() { return true; } } //Enter key performObjectAt selected index. private final class EnterAction extends AbstractAction { static final long serialVersionUID =-239805141416294016L; public EnterAction () { super ("Enter"); // NOI18N } public void actionPerformed(ActionEvent e) { int index = list.getSelectedIndex(); performObjectAt(index, e.getModifiers()); } public boolean isEnabled() { return true; } } } /* * Log * 27 Gandalf 1.26 1/13/00 Ian Formanek NOI18N * 26 Gandalf 1.25 1/12/00 Ian Formanek NOI18N * 25 Gandalf 1.24 1/12/00 Ian Formanek NOI18N * 24 Gandalf 1.23 11/26/99 Patrik Knakal * 23 Gandalf 1.22 11/5/99 Jaroslav Tulach WeakListener has now * registration methods. * 22 Gandalf 1.21 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 21 Gandalf 1.20 8/27/99 Jaroslav Tulach List model can display * more levels at once. * 20 Gandalf 1.19 8/27/99 Jaroslav Tulach New threading model & * Children. * 19 Gandalf 1.18 8/19/99 Ian Formanek Fixed bug 3502 - Switch * between workspaces throws exceptions * 18 Gandalf 1.17 8/9/99 Ian Formanek Generated Serial Version * UID * 17 Gandalf 1.16 7/1/99 Jan Jancura ToolTip support huh.. * 16 Gandalf 1.15 6/28/99 Ian Formanek Fixed bug 2043 - It is * virtually impossible to choose lower items of New From Template from * popup menu on 1024x768 * 15 Gandalf 1.14 6/17/99 David Simonek various serialization * bugfixes * 14 Gandalf 1.13 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 13 Gandalf 1.12 5/17/99 David Simonek DnD switched off :-( * 12 Gandalf 1.11 5/11/99 David Simonek addNotify now run under * Children.Mutex * 11 Gandalf 1.10 4/27/99 David Simonek DnD support in list view * added * 10 Gandalf 1.9 4/16/99 Jan Jancura Object Browser support * 9 Gandalf 1.8 4/9/99 Jan Jancura Bug 1508 * 8 Gandalf 1.7 4/8/99 Jan Jancura invoke&wait & some bug * 7 Gandalf 1.6 4/7/99 Jesse Glick [JavaDoc] * 6 Gandalf 1.5 4/6/99 Ian Formanek Added default handler * 5 Gandalf 1.4 3/20/99 Jesse Glick [JavaDoc] * 4 Gandalf 1.3 3/20/99 Jesse Glick [JavaDoc] * 3 Gandalf 1.2 2/11/99 Jaroslav Tulach SystemAction is * javax.swing.Action * 2 Gandalf 1.1 1/6/99 Ian Formanek Reflecting changes in * location of package "awt" * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ * Beta Change History: * 0 Tuborg 0.14 --/--/98 Jan Formanek added readObject * 0 Tuborg 0.15 --/--/98 Jan Formanek multiple selection enabled * 0 Tuborg 0.16 --/--/98 Jan Formanek backspace+enter keyboard processing * 0 Tuborg 0.20 --/--/98 Jan Formanek reflecting changes in ExplorerView (became abstract class) * 0 Tuborg 0.20 --/--/98 Jan Formanek validating scroll pane repaired * 0 Tuborg 0.22 --/--/98 Jan Formanek repaired repainting * 0 Tuborg 0.30 --/--/98 Jan Formanek SWITCHED TO NODES * 0 Tuborg 0.32 --/--/98 Jan Formanek added listeners for subNodes add/remove and name changes * 0 Tuborg 0.40 --/--/98 Jan Formanek reflecting changes in explorer model * 0 Tuborg 0.42 --/--/98 Jan Formanek fixed clearing of selection after changing Node's name (BUG ID: 01000007) * 0 Tuborg 0.43 --/--/98 Jan Formanek reflecting changes in ExplorerView * 0 Tuborg 0.45 --/--/98 Petr Hamernik doubleclick * 0 Tuborg 0.46 --/--/98 Petr Hamernik default action * 0 Tuborg 0.47 --/--/98 Jan Formanek improved context menu invocation */